'use server';

import path from 'node:path';
import crypto from 'node:crypto';
import fs from 'fs-extra';
import ExifReader from 'exifreader';
import { prisma } from 'src/lib/prisma';
import { Session, withAuth } from './auth';
import { LocalSystemConfig } from './storageSystem';
import { AppStoreSystemConfig } from 'src/lib/AppStoreSystemConfig';
import { Prisma } from 'generated/prisma';

const appStorageSystemConfig = new AppStoreSystemConfig<LocalSystemConfig>();

/**
 * 获取所有公开的图片列表，支持游标分页
 */
export async function getAllPublicImageList(params: {
  /**
   * 搜索文件名
   */
  searchLike?: string;
  /**
   * 过滤条件
   */
  filter?: {
    /**
     * 存储系统 ID
     */
    storageSystemId?: string;
    /**
     * 标签 ID
     */
    tagIds?: string[];
  };
  /**
   * 游标分页
   */
  pagination?: {
    /**
     * 游标 ID（上一页的最后一个项目的 ID）
     */
    cursor?: string;
    /**
     * 每页数量
     * @default 10
     */
    take?: number;
  };
}) {
  const { searchLike, filter, pagination } = params;
  const take = pagination?.take || 10;
  const images = await prisma.image.findMany({
    orderBy: {
      createdAt: 'desc',
    },
    where: {
      isPublic: true,
      fileName: {
        contains: searchLike,
      },
      storageSystemId: filter?.storageSystemId,
      tags: filter?.tagIds?.length
        ? {
            some: {
              id: {
                in: filter.tagIds,
              },
            },
          }
        : undefined,
    },
    take,
    ...(pagination?.cursor && {
      cursor: {
        id: pagination.cursor,
      },
      skip: 1, // 跳过游标所指向的那条记录
    }),
  });
  // 获取下一页的游标
  const nextCursor = images.length === take ? images[images.length - 1].id : null;
  return {
    data: images,
    pagination: {
      nextCursor,
      hasMore: nextCursor !== null,
    },
  };
}

async function _getImageList(
  session: Session,
  params: {
    /**
     * 文件夹 ID
     * @description 如果传入 null，表示根目录；如果传入 undefined，表示所有目录
     * @default undefined
     */
    folderId?: string | null;
    /**
     * 搜索文件名
     */
    searchLike?: string;
    /**
     * 过滤条件
     */
    filter?: {
      /**
       * 存储系统 ID
       */
      storageSystemIds?: string[];
      /**
       * 标签 ID
       */
      tagIds?: string[];
      /**
       * 是否公开
       */
      isPublic?: boolean;
    };
    /**
     * 排序条件
     */
    orderBy?: {
      /**
       * 按文件名排序
       */
      fileName?: 'asc' | 'desc';
      /**
       * 按创建时间排序
       * @default 'desc'
       */
      createdAt?: 'asc' | 'desc';
    };
    /**
     * 游标分页
     */
    pagination?: {
      /**
       * 游标 ID（上一页的最后一个项目的 ID）
       */
      cursor?: string;
      /**
       * 每页数量
       * @default 10
       */
      take?: number;
    };
  }
) {
  const { folderId, searchLike, filter, orderBy, pagination } = params;
  const take = pagination?.take || 10;
  const images = await prisma.image.findMany({
    orderBy: [
      orderBy?.fileName
        ? {
            fileName: orderBy.fileName,
          }
        : {},
      {
        createdAt: orderBy?.createdAt || 'desc',
      },
    ],
    where: {
      userId: session.userId,
      folderId, // 传入 null，表示根目录，传 undefined，表示所有目录
      fileName: searchLike
        ? {
            contains: searchLike,
          }
        : undefined,
      isPublic: filter?.isPublic,
      // 过滤存储系统
      storageSystemId: filter?.storageSystemIds?.length
        ? {
            in: filter.storageSystemIds,
          }
        : undefined,
      // 过滤标签
      tags: filter?.tagIds?.length
        ? {
            some: {
              id: {
                in: filter.tagIds,
              },
            },
          }
        : undefined,
    },
    include: {
      tags: {
        select: {
          id: true,
          name: true,
        },
      },
      storageSystem: {
        select: {
          id: true,
          name: true,
          type: true,
        },
      },
    },
    take,
    ...(pagination?.cursor && {
      cursor: {
        id: pagination.cursor,
      },
      skip: 1, // 跳过游标所指向的那条记录，从游标之后的下一条记录开始获取数据，如果没有 skip: 1，Prisma 会从游标所指向的记录开始返回数据，这会导致该记录在两页中重复出现。
    }),
  });
  // 获取下一页的游标
  const nextCursor = images.length === take ? images[images.length - 1].id : null;
  return {
    list: images,
    pagination: {
      nextCursor,
      hasMore: nextCursor !== null,
    },
  };
}

/**
 * 获取用户资源图片列表，支持游标分页
 */
export const getImageList = withAuth(_getImageList);

async function _getImage(session: Session, id: string) {
  const image = await prisma.image.findUnique({
    where: {
      id,
      userId: session.userId,
    },
  });
  return image;
}

/**
 * 获取用户资源图片详情
 */
export const getImage = withAuth(_getImage);

async function _updateImage(
  session: Session,
  params: {
    id: string;
    data: {
      alt?: string;
      isPublic?: boolean;
      tags?: string[];
      folderId?: string;
    };
  }
) {
  const { id, data } = params;
  const { alt, isPublic, folderId, tags } = data;
  const image = await prisma.image.update({
    where: {
      id,
      userId: session.userId,
    },
    data: {
      alt,
      isPublic,
      folderId,
      tags: {
        set: tags?.map((tag) => ({ id: tag })),
      },
    },
  });
  return image;
}

/**
 * 更新用户资源图片
 */
export const updateImage = withAuth(_updateImage);

async function _insertImage(
  session: Session,
  params: {
    file: File;
    alt?: string;
    storageSystemId: string;
    isPublic?: boolean;
    tags?: string[];
    folderId?: string;
  }
) {
  const { file, alt, storageSystemId, isPublic, folderId, tags } = params;

  let relativePath: string | undefined; // 本地存储类型，服务器存储路径
  let originalUrl: string | undefined; // 云存储类型，原始资源路由
  let publicUrl: string = '/'; // 所有存储类型：公共可访问路由，使用三段式拼成(/[storageSystemName]/[folder]/[hash_name].[ext])

  // 1.校验：图片大小不能高于 50MB
  if (file.size > 50 * 1024 * 1024) {
    throw new Error('文件大小不能超过 50MB');
  }

  // 2.校验：图片格式限制
  const allowedMimeTypes = [
    'image/jpeg',
    'image/png',
    'image/gif',
    'image/webp',
    'image/avif',
    'image/tiff',
    'image/svg+xml',
  ];
  if (!allowedMimeTypes.includes(file.type)) {
    throw new Error('不支持的文件格式');
  }

  // 3.校验：图片是否存在
  const arrayBuffer = await file.arrayBuffer();
  const buffer = Buffer.from(arrayBuffer);
  const hash = crypto.createHash('md5').update(buffer).digest('hex'); // 生成图片哈希值：md5(较快但安全性较低)
  // 检查是否存在相同哈希值的图片（防止重复上传）
  const existingImage = await prisma.image.findUnique({
    where: { hash },
  });
  if (existingImage) {
    // TODO: 不能直接在 server action 中抛出错误，应该返回一个错误结果，否则生产环境会导致产生错误日志，例如
    //  return { error: '图片已存在，请勿重复上传' };
    throw new Error('图片已存在，请勿重复上传');
  }

  // 4.校验：存储系统配置是否存在
  const storageSystem = await appStorageSystemConfig.getConfig(storageSystemId);
  if (!storageSystem) {
    throw new Error('该存储系统的环境变量配置不存在');
  }
  const storageSystemType = storageSystem.type;
  publicUrl += storageSystem.name; // 公共可访问路由：拼接存储实例名称

  // 5.校验：FolderId 是否存在
  if (folderId) {
    const folder = await prisma.folder.findUnique({
      where: {
        id: folderId,
        userId: session.userId,
      },
    });
    if (!folder) {
      throw new Error('该文件夹不存在');
    }
    publicUrl += `/${folder.name}`;
  }

  // 私有存储类型：local
  if (storageSystemType === 'local') {
    let basePath: string | undefined;
    const config = storageSystem?.config;
    if (config && config.basePath) {
      basePath = AppStoreSystemConfig.parseSystemPath(config.basePath);
      console.log('basePath', basePath);
    }
    if (!basePath) {
      throw new Error('存储系统配置错误，basePath 不存在');
    }
    // 图片相对路径：年/月/日/时间戳_文件名，例如：2023/10/01/1696128000000_1.jpg
    const date = new Date();
    const year = date.getFullYear().toString();
    const month = String(date.getMonth() + 1).padStart(2, '0');
    const day = String(date.getDate()).padStart(2, '0');
    const shortId = crypto.randomBytes(6).toString('hex'); // 12个字符的随机字符串
    const fileName = `${date.getTime()}_${shortId}_${file.name}`; // 真实存储名称，防止重名
    publicUrl += `/${fileName}`; // 公共可访问路由：拼接成三段式路径
    relativePath = path.join(year, month, day, fileName); // 真实存储路径：相对路径，记录到数据库
    const filePath = path.join(basePath, relativePath); // 真实存储路径
    // 创建目录
    await fs.ensureDir(path.dirname(filePath));
    // 写入私有文件系统盘
    await fs.writeFile(filePath, buffer);
  }

  // TODO: 云存储类型：云存储系统类型的适配，例如：oss, kodo 等，注意拼接 publicUrl

  // 写入数据库
  const exif = await ExifReader.load(buffer, { async: true }); // 获取图片元信息
  const width = exif['Image Width']?.value || exif.ImageWidth?.value;
  const height = exif['Image Height']?.value || exif.ImageHeight?.value;
  const metadata: Record<string, unknown> = {};
  const collectKeys: { key: keyof typeof exif; label: string }[] = [
    { key: 'Image Width', label: '宽度' },
    { key: 'Image Height', label: '高度' },
    { key: 'FileType', label: '文件类型' },
    { key: 'DateTimeOriginal', label: '拍摄时间' },
    { key: 'Bits Per Sample', label: '样本位数' },
    { key: 'Technology', label: '技术' },
    { key: 'Make', label: '照相机制造商' },
    { key: 'Model', label: '照相机型号' },
    { key: 'FocalLength', label: '焦距' },
    { key: 'LensModel', label: '镜头型号' },
    { key: 'ColorSpace', label: '色彩表示' },
    { key: 'Software', label: '软件' },
    { key: 'ExposureTime', label: '曝光时间' },
    { key: 'FNumber', label: '光圈' },
    { key: 'ISOSpeedRatings', label: 'ISO' },
    { key: 'ExposureProgram', label: '曝光程序' },
    { key: 'MeteringMode', label: '测光模式' },
    { key: 'Flash', label: '闪光灯' },
    { key: 'WhiteBalance', label: '白平衡' },
    { key: 'ExposureBiasValue', label: '曝光补偿' },
    { key: 'ExifVersion', label: 'Exif版本' },
  ];
  collectKeys.forEach((c) => {
    const value = exif[c.key]?.description || exif[c.key]?.value;
    if (value) {
      metadata[c.label] = value;
    }
  });
  const image = await prisma.image.create({
    data: {
      userId: session.userId,
      fileName: file.name,
      mimeType: file.type,
      width,
      height,
      fileSize: file.size,
      alt,
      isPublic,
      publicUrl,
      storagePath: relativePath,
      originalUrl,
      storageSystemId,
      metadata: metadata as unknown as Prisma.InputJsonValue,
      hash,
      folderId,
      tags: {
        connect: tags?.map((tag) => ({ id: tag })),
      },
    },
  });
  return image;
}

/**
 * 新增用户资源图片
 */
export const insertImage = withAuth(_insertImage);
